﻿using App.DbContexts;
using App.DbContexts.TestEntities;
using App.Models;
using App.UnitOfWork;
using App.Utils;
using Microsoft.Data.SqlClient;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.EntityFrameworkCore;

namespace App.Business
{

    /// <summary>
    /// 结论：使用工作单元（注入），由工作单元创建仓储，可以保证同一个上下文，可以实现事务(跨库不能实现事务)
    /// 使用非事务时，可以直接使用注入的仓储。
    /// </summary>
    public class TestBusiness2
    {
        public IUnitOfWork<AppTestDBContext> _unitOfWork_1 { get; set; }//不要用工作单元，除非你要开启事务
        public IUnitOfWork<AppTestDB2Context> _unitOfWork_2 { get; set; }//不要用工作单元，除非你要开启事务

        public IRepository<TestEntity> _testRepository { get; set; }//正常业务使用这种，够了
        public IRepository<TestDetailEntity> _testDetailRepository { get; set; }//正常业务使用这种，够了

        /// <summary>
        /// 涉及到事务：请使用工作单元（正常的执行成功）
        /// </summary>
        public void Test()
        {
            var testRepository = _unitOfWork_1.GetRepository<TestEntity>();
            var testDetailRepository = _unitOfWork_1.GetRepository<TestDetailEntity>();
            var tests = testRepository.QueryAble().ToList();
            using (var tran = _unitOfWork_1.BeginTransaction())
            {
                try
                {
                    var testModel = new TestEntity() { Name1 = "主从", Name2 = "事务" };
                    var test = testRepository.Insert(testModel);//注意看test.Id,和testModel.Id都会有值。

                    var testDetailModel = new TestDetailEntity() { TestId = test.Id, Detail1 = "Detail1", Detail2 = "Detail2" };
                    var testDetail = testDetailRepository.Insert(testDetailModel);

                    tran.Commit();
                }
                catch (Exception)
                {
                    tran.Rollback();
                    throw;
                }

            }

            var blogRepository = _unitOfWork_2.GetRepository<BlogEntity>();
            var blogs = blogRepository.QueryAble().ToList();
        }

        /// <summary>
        /// 工作单元：（执行失败）事务回滚
        /// </summary>
        public void Test2()
        {
            var testRepository = _unitOfWork_1.GetRepository<TestEntity>();
            var testDetailRepository = _unitOfWork_1.GetRepository<TestDetailEntity>();
            using (var tran = _unitOfWork_1.BeginTransaction())
            {
                try
                {
                    var testModel = new TestEntity() { Name1 = "主从", Name2 = "事务" };
                    var test = testRepository.Insert(testModel);//注意看test.Id,和testModel.Id都会有值。

                    var testDetailModel = new TestDetailEntity()
                    {
                        TestId = test.Id,
                        Detail1 = "Detail1asdfasdfasdfasdfasdsdfasfasd",//数据库表字段长度为20，这里超出，报错(模拟各种会失败的场景)
                        Detail2 = "Detail2"
                    };
                    var testDetail = testDetailRepository.Insert(testDetailModel);

                    tran.Commit();
                }
                catch (Exception)
                {
                    tran.Rollback();
                    throw;
                }

            }
            _unitOfWork_1.SaveChanges();
        }

        /// <summary>
        /// 不需要事务的操作、查询，都可以直接使用仓储。
        /// 不使用工作单元，直接仓储
        /// </summary>
        public void Test3()
        {
            var dd = _testRepository.QueryAble().ToList();
            _testRepository.Insert(new TestEntity()
            {
                Name1 = "不需要事务的操作、查询，都可以直接使用仓储",
                Name2 = "不使用工作单元，直接仓储"
            });
        }

        /// <summary>
        /// 不使用工作单元，直接仓储，是否支持事务(结论：不支持，不是同一个上下文)
        /// 都不要用以下这种模式写法。工作单元和仓储不在同一个上下文。
        /// 结论：使用事务，必须由工作单元，进行GetRepository()后的仓储，才能保证事务
        /// 以下是错误代码示例!!!!!
        /// </summary>
        public void Test4()
        {
            //错误示范
            using (var tran = _unitOfWork_2.BeginTransaction())
            {
                try
                {
                    var testModel = new TestEntity() { Name1 = "主从", Name2 = "事务" };
                    var test = _testRepository.Insert(testModel);//这个会直接保存成功了！

                    var testDetailModel = new TestDetailEntity()
                    {
                        TestId = test.Id,
                        Detail1 = "Detail1asdfasdfasdfasdfasdsdfasfasd",//数据库表字段长度为20，这里超出，报错(模拟各种会失败的场景)
                        Detail2 = "Detail2"
                    };
                    var testDetail = _testDetailRepository.Insert(testDetailModel);//这个异常了，但是没有回滚（_testRepository，_testDetailRepository虽然是同一个上下文，但是外层是另一个上下文包的事务，不起作用）
                    tran.Commit();
                }
                catch (Exception)
                {
                    tran.Rollback();
                    throw;
                }
            }

        }

        /// <summary>
        /// 查看实体的id是否回来
        /// 结论：insert/inserts实体ID会获取回来。
        /// </summary>
        public void Test5()
        {
            var insert = new TestEntity() { Name1 = "单个插入", Name2 = "获取到id哦" };
            var test = _testRepository.Insert(insert);//注意看insert、test的ID字段，可以获取回来

            var inserts = new List<TestEntity>() {
                    new TestEntity(){ Name1 = "批量插入1", Name2 = "获取到id哦" },
                    new TestEntity(){ Name1 = "批量插入2", Name2 = "获取到id哦" },
                    new TestEntity(){ Name1 = "批量插入3", Name2 = "获取到id哦" },
                    new TestEntity(){ Name1 = "批量插入4", Name2 = "获取到id哦" },
                };
            _testRepository.Insert(inserts);//注意看inserts的ID字段，可以获取回来
        }

        /// <summary>
        /// 执行sql的方式（插入，修改，删除等等）
        /// </summary>
        public void Test6()
        {
            var sql_insert = @$"insert Test (Name1,Name2) values ('sql','sql')";
            var exceRow = _unitOfWork_1.ExecuteSqlCommand(sql_insert);

            var sql_insert22 = @"insert Test (Name1,Name2) values ({0},{1})";
            var exceRow22 = _unitOfWork_1.ExecuteSqlCommand(sql_insert22, "sql2", "sql2");

            var sql_insert3 = @$"insert Test (Name1,Name2) values (@jjj,@qqq)";
            var exceRow3 = _unitOfWork_1.ExecuteSqlCommand(sql_insert3, new SqlParameter("jjj", "jsql"), new SqlParameter("qqq", "qqqsql"));
        }

        /// <summary>
        /// 执行sql的方式（查询等等）
        /// </summary>
        public void Test7()
        {
            var sql_Query = @$"select * from  Test (nolock)";
            var testEntity = _unitOfWork_1.FromSql<TestEntity>(sql_Query).ToList();

            var sql_Query2 = @$"select Test.Id,Test.Name1,Test.Name2,TestDetial.Detail2 from  Test (nolock) left join TestDetial on Test.Id=TestDetial.TestId";
            //var testEntity2 = _unitOfWork_1.FromSql<TestDto>(sql_Query2).ToList();//失败了。只能数据库实体。(泛型约束)
            var testEntity2 = _unitOfWork_1.FromSql<TestEntity>(sql_Query2).ToList();//失败了。只能数据库实体。
        }

        /// <summary>
        /// IOC测试。有时候，在我们不方便注入属性时，可以使用以下方法，直接从IOC里拿到对象(都是请求作用域生命周期的)
        /// </summary>
        public void TestIoc()
        {
            var testBusiness = IocManager.Instance.GetService<TestBusiness>();//获取Business
            var testDetailRepository = IocManager.Instance.GetService<IRepository<TestDetailEntity>>();//获取仓储

            //以上都是可行的。原理在App.Web 下面的Program.cs 使用了Autofac接管了.net core原生的IOC,
            //并且有配置和注入进去。
            //而我们自己写的IocManager.Instance.GetService 就是对Autofac的IOC进行访问获取对象。

            testBusiness.Query();

            var count = testDetailRepository.Count();

        }


        /// <summary>
        /// 分页：这个分页结果让人很舒服，全面的很
        /// 具体看PagedList<T>
        /// 起始页,当前页,每页大小,数据总数,总页数,当前页数据(List<T>),是否有上一页,是否有下一页
        /// </summary>
        public void TestPage()
        {
            var data1 = _testRepository.GetPagedList();//默认20条，第一页

            //带条件，又带页数页码
            var data2 = _testRepository.GetPagedList(
             predicate: w => w.Name1 == "如何解决",
             pageIndex: 2,
             pageSize: 3);

            var data3 = _testRepository.GetPagedList(
                predicate: w => w.Name1 == "如何解决",
                orderBy: q => q.OrderByDescending(o => o.Name1),
                //include: q => q.Include(x=>x.Name1),//不会用,懂的指教一下
                pageIndex: 1,
                pageSize: 3
            );

        }

        /// <summary>
        /// 连表和分页
        /// </summary>
        public void TestJoinAndPage()
        {
            //我发现这个2个同一个IOC出来是同一个上下文
            var testQuery = _testRepository.QueryAble();
            var testDetialQuery = _testDetailRepository.QueryAble();

            //查看后，发现这2个仓储都是同一个上下文（上下文ID相等）

            //(((App.UnitOfWork.Repository<App.DbContexts.TestDetailEntity>)_testDetailRepository)._dbContext.ContextId.InstanceId.ToString()) ="461b9840-2fe6-40ba-a07a-4f018de658f4"

            //(((App.UnitOfWork.Repository<App.DbContexts.TestEntity>)_testRepository)._dbContext.ContextId.InstanceId.ToString())="461b9840-2fe6-40ba-a07a-4f018de658f4"

            //_unitOfWork_1.DbContext.ContextId.ToString() = "c9160161-ac87-494f-9683-39b3c28fce23:0"

            //_unitOfWork_2.DbContext.ContextId.ToString() = "2134888f-52bf-4b98-a6d6-bc549d3f005d:0"

            //结论就是每一个工作单元是独立的一个上下文，仓储都是同一个上下文
            //所以下面这个就没毛病。
            var query = from test in testQuery
                        join testDetail in testDetialQuery
                        on test.Id equals testDetail.Id
                        select new TestDto
                        {
                            Id = test.Id,
                            Name1 = test.Name1,
                            Name2 = test.Name2,
                            Detail2 = testDetail.Detail2,
                        };
            var data = query.ToList();

            //试试这个，应该有毛病：因为2个不同的上下文进行连表，肯定有问题
            //var testDetailFromUnitOfWork= _unitOfWork_1.GetRepository<TestDetailEntity>().QueryAble();
            //var query2 = from test in testQuery
            //            join testDetail in testDetailFromUnitOfWork
            //            on test.Id equals testDetail.Id
            //            select new TestDto
            //            {
            //                Id = test.Id,
            //                Name1 = test.Name1,
            //                Name2 = test.Name2,
            //                Detail2 = testDetail.Detail2,
            //            };
            //var data2 = query2.ToList();

            //连表后分页
            var query3 = from test in testQuery
                         join testDetail in testDetialQuery
                         on test.Id equals testDetail.Id
                         select new TestDto
                         {
                             Id = test.Id,
                             Name1 = test.Name1,
                             Name2 = test.Name2,
                             Detail2 = testDetail.Detail2,
                         };
            var pageData = query3.OrderBy(o => o.Id).ToPagedList(1, 10);

        }

    }

}

